/*
Copyright (C) 2011 The University of Michigan

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Please send inquiries to powertutor@umich.edu
 */

package vn.cybersoft.obs.andriod.batterystats2.components;

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.provider.Settings;
import android.os.Process;
import android.util.Log;
import android.util.DisplayMetrics;
import android.view.WindowManager;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Random;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;

import vn.cybersoft.obs.andriod.batterystats2.phone.PhoneConstants;
import vn.cybersoft.obs.andriod.batterystats2.service.IterationData;
import vn.cybersoft.obs.andriod.batterystats2.service.PowerData;
import vn.cybersoft.obs.andriod.batterystats2.util.ForegroundDetector;
import vn.cybersoft.obs.andriod.batterystats2.util.NativeLoader;
import vn.cybersoft.obs.andriod.batterystats2.util.Recycler;
import vn.cybersoft.obs.andriod.batterystats2.util.SystemInfo;

public class OLED extends PowerComponent {
	public static class OledData extends PowerData {
		private static Recycler<OledData> recycler = new Recycler<OledData>();

		public static OledData obtain() {
			OledData result = recycler.obtain();
			if (result != null)
				return result;
			return new OledData();
		}

		@Override
		public void recycle() {
			recycler.recycle(this);
		}

		public int brightness;
		public double pixPower;
		public boolean screenOn;

		private OledData() {
		}

		public void init() {
			this.screenOn = false;
		}

		public void init(int brightness, double pixPower) {
			screenOn = true;
			this.brightness = brightness;
			this.pixPower = pixPower;
		}

		public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
			out.write("OLED-brightness " + brightness + "\n");
			out.write("OLED-pix-power " + pixPower + "\n");
			out.write("OLED-screen-on " + screenOn + "\n");
		}
	}

	private static final String TAG = "OLED";
	private static final String[] BACKLIGHT_BRIGHTNESS_FILES = {
		"/sys/class/leds/lcd-backlight/brightness",
		"/sys/devices/virtual/leds/lcd-backlight/brightness",
		"/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness", };

	private Context context;
	private ForegroundDetector foregroundDetector;
	private BroadcastReceiver broadcastReceiver;
	private boolean screenOn;

	private File frameBufferFile;

	private int screenWidth;
	private int screenHeight;

	private static final int NUMBER_OF_SAMPLES = 500;
	private int[] samples;

	private String brightnessFile;

	/*
	 * Coefficients pre-computed for pix power calculations.
	 */
	private double rcoef;
	private double gcoef;
	private double bcoef;
	private double modul_coef;

	public OLED(Context context, PhoneConstants constants) {
		this.context = context;
		screenOn = true;

		foregroundDetector = new ForegroundDetector(
				(ActivityManager) context
				.getSystemService(context.ACTIVITY_SERVICE));
		broadcastReceiver = new BroadcastReceiver() {
			public void onReceive(Context context, Intent intent) {
				synchronized (this) {
					if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
						screenOn = false;
					} else if (intent.getAction().equals(
							Intent.ACTION_SCREEN_ON)) {
						screenOn = true;
					}
				}
			};
		};
		IntentFilter intentFilter = new IntentFilter();
		intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
		intentFilter.addAction(Intent.ACTION_SCREEN_ON);
		context.registerReceiver(broadcastReceiver, intentFilter);

		frameBufferFile = new File("/dev/fb0");
		if (!frameBufferFile.exists()) {
			frameBufferFile = new File("/dev/graphics/fb0");
		}
		if (frameBufferFile.exists())
			try {
				/* Check if we already have permission to read the frame buffer. */
				boolean readOk = false;
				try {
					RandomAccessFile fin = new RandomAccessFile(
							frameBufferFile, "r");
					int b = fin.read();
					fin.close();
					readOk = true;
				} catch (IOException e) {
				}
				/* Don't have permission, try to change permission as root. */
				if (!readOk) {
					java.lang.Process p = Runtime.getRuntime().exec("su");
					DataOutputStream os = new DataOutputStream(
							p.getOutputStream());
					os.writeBytes("chown " + android.os.Process.myUid() + " "
							+ frameBufferFile.getAbsolutePath() + "\n");
					os.writeBytes("chown app_"
							+ (android.os.Process.myUid() - SystemInfo.AID_APP)
							+ " " + frameBufferFile.getAbsolutePath() + "\n");
					os.writeBytes("chmod 660 "
							+ frameBufferFile.getAbsolutePath() + "\n");
					os.writeBytes("exit\n");
					os.flush();
					p.waitFor();
					if (p.exitValue() != 0) {
						Log.i(TAG,
								"failed to change permissions on frame buffer");
					}
				}
			} catch (InterruptedException e) {
				Log.i(TAG, "changing permissions on frame buffer interrupted");
			} catch (IOException e) {
				Log.i(TAG, "unexpected exception while changing permission on "
						+ "frame buffer");
				e.printStackTrace();
			}

		DisplayMetrics metrics = new DisplayMetrics();
		WindowManager windowManager = (WindowManager) context
				.getSystemService(Context.WINDOW_SERVICE);
		windowManager.getDefaultDisplay().getMetrics(metrics);
		screenWidth = metrics.widthPixels;
		screenHeight = metrics.heightPixels;

		Random r = new Random();
		samples = new int[NUMBER_OF_SAMPLES];
		for (int i = 0; i < NUMBER_OF_SAMPLES; i++) {
			int a = screenWidth * screenHeight * i / NUMBER_OF_SAMPLES;
			int b = screenWidth * screenHeight * (i + 1) / NUMBER_OF_SAMPLES;
			samples[i] = a + r.nextInt(b - a);
		}

		double[] channel = constants.oledChannelPower();
		rcoef = channel[0] / 255 / 255;
		gcoef = channel[1] / 255 / 255;
		bcoef = channel[2] / 255 / 255;
		modul_coef = constants.oledModulation() / 255 / 255 / 3 / 3;

		for (int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) {
			if (new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) {
				brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i];
			}
		}
	}

	@Override
	protected void onExit() {
		context.unregisterReceiver(broadcastReceiver);
		super.onExit();
	}

	@Override
	public IterationData calculateIteration(long iteration) {
		IterationData result = IterationData.obtain();

		boolean screen;
		synchronized (this) {
			screen = screenOn;
		}

		int brightness;
		if (brightnessFile != null) {
			brightness = (int) SystemInfo.getInstance().readLongFromFile(
					brightnessFile);
		} else {
			try {
				brightness = Settings.System.getInt(
						context.getContentResolver(),
						Settings.System.SCREEN_BRIGHTNESS);
			} catch (Settings.SettingNotFoundException ex) {
				Log.w(TAG, "Could not retrieve brightness information");
				return result;
			}
		}
		if (brightness < 0 || 255 < brightness) {
			Log.w(TAG, "Could not retrieve brightness information");
			return result;
		}

		double pixPower = 0;
		if (screen && frameBufferFile.exists()) {
			if (NativeLoader.jniLoaded()) {
				pixPower = getScreenPixPower(rcoef, gcoef, bcoef, modul_coef);
			} else
				try {
					RandomAccessFile fin = new RandomAccessFile(
							frameBufferFile, "r");

					for (int x : samples) {
						fin.seek(x * 4);
						int px = fin.readInt();
						int b = px >> 8 & 0xFF;
		int g = px >> 16 & 0xFF;
		int r = px >> 24 & 0xFF;

		/*
		 * Calculate the power usage of this one pixel if it
		 * were at full brightness. Linearly scale by brightness
		 * to get true power consumption. To calculate whole
		 * screen compute average of sampled region and multiply
		 * by number of pixels.
		 */
		int modul_val = r + g + b;
		pixPower += rcoef * (r * r) + gcoef * (g * g) + bcoef
				* (b * b) - modul_coef
				* (modul_val * modul_val);
					}
					fin.close();
				} catch (FileNotFoundException e) {
					pixPower = -1;
				} catch (IOException e) {
					pixPower = -1;
					e.printStackTrace();
				}
			if (pixPower >= 0) {
				pixPower *= 1.0 * screenWidth * screenHeight
						/ NUMBER_OF_SAMPLES;
			}
		}

		OledData data = OledData.obtain();
		if (!screen) {
			data.init();
		} else {
			data.init(brightness, pixPower);
		}
		result.setPowerData(data);

		if (screen) {
			OledData uidData = OledData.obtain();
			uidData.init(brightness, pixPower);
			result.addUidPowerData(foregroundDetector.getForegroundUid(),
					uidData);
		}

		return result;
	}

	@Override
	public boolean hasUidInformation() {
		return true;
	}

	@Override
	public String getComponentName() {
		return "OLED";
	}

	public static native double getScreenPixPower(double rcoef, double gcoef,
			double bcoef, double modul_coef);
}
